const {
	isValidString,
	getType
} = require('./utils.js')
const {
	ERROR
} = require('./error')

const baseValidator = Object.create(null)

baseValidator.username = function(username) {
	const errCode = ERROR.INVALID_USERNAME
	if (!isValidString(username)) {
		return {
			errCode
		}
	}
	// if (/^\d+$/.test(username)) {
	// 	// 用户名不能为纯数字
	// 	return {
	// 		errCode
	// 	}
	// };
	// if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
	// 	// 用户名仅能使用数字、字母、“_”及“-”
	// 	return {
	// 		errCode
	// 	}
	// }
}

baseValidator.password = function(password) {
	const errCode = ERROR.INVALID_PASSWORD
	if (!isValidString(password)) {
		return {
			errCode
		}
	}
	if (password.length < 1) {
		// 密码长度不能小于6
		return {
			errCode
		}
	}
}

baseValidator.mobile = function(mobile) {
	const errCode = ERROR.INVALID_MOBILE
	if (getType(mobile) !== 'string') {
		return {
			errCode
		}
	}
	if (mobile && !/^1\d{10}$/.test(mobile)) {
		return {
			errCode
		}
	}
}

baseValidator.email = function(email) {
	const errCode = ERROR.INVALID_EMAIL
	if (getType(email) !== 'string') {
		return {
			errCode
		}
	}
	if (email && !/@/.test(email)) {
		return {
			errCode
		}
	}
}

baseValidator.nickname = function(nickname) {
	const errCode = ERROR.INVALID_NICKNAME
	if (nickname.indexOf('@') !== -1) {
		// 昵称不允许含@
		return {
			errCode
		}
	};
	if (/^\d+$/.test(nickname)) {
		// 昵称不能为纯数字
		return {
			errCode
		}
	};
	if (nickname.length > 100) {
		// 昵称不可超过100字符
		return {
			errCode
		}
	}
}

const baseType = ['string', 'boolean', 'number', 'null'] // undefined不会由客户端提交上来

baseType.forEach((type) => {
	baseValidator[type] = function(val) {
		if (getType(val) === type) {
			return
		}
		return {
			errCode: ERROR.INVALID_PARAM
		}
	}
})

function tokenize(name) {
	let i = 0
	const result = []
	let token = ''
	while (i < name.length) {
		const char = name[i]
		switch (char) {
			case '|':
			case '<':
			case '>':
				token && result.push(token)
				result.push(char)
				token = ''
				break
			default:
				token += char
				break
		}
		i++
		if (i === name.length && token) {
			result.push(token)
		}
	}
	return result
}

/**
 * 处理validator名
 * @param {string} name
 */
function parseValidatorName(name) {
	const tokenList = tokenize(name)
	let i = 0
	let currentToken = tokenList[i]
	const result = {
		type: 'root',
		children: [],
		parent: null
	}
	let lastRealm = result
	while (currentToken) {
		switch (currentToken) {
			case 'array': {
				const currentRealm = {
					type: 'array',
					children: [],
					parent: lastRealm
				}
				lastRealm.children.push(currentRealm)
				lastRealm = currentRealm
				break
			}
			case '<':
				if (lastRealm.type !== 'array') {
					throw new Error('Invalid validator token "<"')
				}
				break
			case '>':
				if (lastRealm.type !== 'array') {
					throw new Error('Invalid validator token ">"')
				}
				lastRealm = lastRealm.parent
				break
			case '|':
				if (lastRealm.type !== 'array' && lastRealm.type !== 'root') {
					throw new Error('Invalid validator token "|"')
				}
				break
			default:
				lastRealm.children.push({
					type: currentToken
				})
				break
		}
		i++
		currentToken = tokenList[i]
	}
	return result
}

function getRuleCategory(rule) {
	switch (rule.type) {
		case 'array':
			return 'array'
		case 'root':
			return 'root'
		default:
			return 'base'
	}
}


// 特殊符号 https://www.ibm.com/support/pages/password-strength-rules  ~!@#$%^&*_-+=`|\(){}[]:;"'<>,.?/
// const specialChar = '~!@#$%^&*_-+=`|\(){}[]:;"\'<>,.?/'
// const specialCharRegExp = /^[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]$/
// for (let i = 0, arr = specialChar.split(''); i < arr.length; i++) {
//   const char = arr[i]
//   if (!specialCharRegExp.test(char)) {
//     throw new Error('check special character error: ' + char)
//   }
// }

// 密码强度表达式
const passwordRules = {

	// 密码必须包含大小写字母、数字和特殊符号
	super: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
	// 密码必须包含字母、数字和特殊符号
	strong: /^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
	// 密码必须为字母、数字和特殊符号任意两种的组合
	medium: /^(?![0-9]+$)(?![a-zA-Z]+$)(?![~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]+$)[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/,
	// 密码必须包含字母和数字
	weak: /^(?=.*[0-9])(?=.*[a-zA-Z])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{6,16}$/,

}

function createPasswordVerifier({
	passwordStrength = ''
} = {}) {
	return function(password) {
		// 校验密码
		// const passwordRegExp = passwordRules[passwordStrength]
		if (!passwordRegExp) {
			throw new Error('Invalid password strength config: ' + passwordStrength)
		}
		const errCode = ERROR.INVALID_PASSWORD
		if (!isValidString(password)) {
			return {
				errCode
			}
		}
		if (!passwordRegExp.test(password)) {
			return {
				errCode: errCode + '-' + passwordStrength
			}
		}
	}
}

function isEmpty(value) {
	return value === undefined ||
		value === null ||
		(typeof value === 'string' && value.trim() === '')
}

class Validator {
	constructor({
		passwordStrength = ''
	} = {}) {
		this.baseValidator = baseValidator
		this.customValidator = Object.create(null)
		if (passwordStrength) {
			this.mixin(
				'password',
				createPasswordVerifier({
					passwordStrength
				})
			)
		}
	}

	mixin(type, handler) {
		this.customValidator[type] = handler
	}

	getRealBaseValidator(type) {
		return this.customValidator[type] || this.baseValidator[type]
	}


	_isMatchUnionType(val, rule) {
		if (!rule.children || rule.children.length === 0) {
			return true
		}
		const children = rule.children
		for (let i = 0; i < children.length; i++) {
			const child = children[i]
			const category = getRuleCategory(child)
			let pass = false
			switch (category) {
				case 'base':
					pass = this._isMatchBaseType(val, child)
					break
				case 'array':
					pass = this._isMatchArrayType(val, child)
					break
				default:
					break
			}
			if (pass) {
				return true
			}
		}
		return false
	}

	_isMatchBaseType(val, rule) {
		const method = this.getRealBaseValidator(rule.type)
		if (typeof method !== 'function') {
			throw new Error(`invalid schema type: ${rule.type}`)
		}
		const validateRes = method(val)
		if (validateRes && validateRes.errCode) {
			return false
		}
		return true
	}

	_isMatchArrayType(arr, rule) {
		if (getType(arr) !== 'array') {
			return false
		}
		if (rule.children && rule.children.length && arr.some(item => !this._isMatchUnionType(item, rule))) {
			return false
		}
		return true
	}

	get validator() {
		const _this = this
		return new Proxy({}, {
			get: (_, prop) => {
				if (typeof prop !== 'string') {
					return
				}
				const realBaseValidator = this.getRealBaseValidator(prop)
				if (realBaseValidator) {
					return realBaseValidator
				}
				const rule = parseValidatorName(prop)
				return function(val) {
					if (!_this._isMatchUnionType(val, rule)) {
						return {
							errCode: ERROR.INVALID_PARAM
						}
					}
				}
			}
		})
	}

	validate(value = {}, schema = {}) {
		for (const schemaKey in schema) {
			let schemaValue = schema[schemaKey]
			if (getType(schemaValue) === 'string') {
				schemaValue = {
					required: true,
					type: schemaValue
				}
			}
			const {
				required,
				type
			} = schemaValue
			// value内未传入了schemaKey或对应值为undefined
			if (isEmpty(value[schemaKey])) {
				if (required) {
					return {
						errCode: ERROR.PARAM_REQUIRED,
						errMsgValue: {
							param: schemaKey
						},
						schemaKey
					}
				} else {
					//delete value[schemaKey]
					continue
				}
			}
			const validateMethod = this.validator[type]
			if (!validateMethod) {
				throw new Error(`invalid schema type: ${type}`)
			}
			const validateRes = validateMethod(value[schemaKey])
			if (validateRes) {
				validateRes.schemaKey = schemaKey
				return validateRes
			}
		}
	}
}

function checkClientInfo(clientInfo) {
	const stringNotRequired = {
		required: false,
		type: 'string'
	}
	const numberNotRequired = {
		required: false,
		type: 'number'
	}
	const numberOrStringNotRequired = {
		required: false,
		type: 'number|string'
	}
	const schema = {
		uniPlatform: 'string',
		appId: 'string',
		deviceId: stringNotRequired,
		osName: stringNotRequired,
		locale: stringNotRequired,
		clientIP: stringNotRequired,
		appName: stringNotRequired,
		appVersion: stringNotRequired,
		appVersionCode: numberOrStringNotRequired,
		channel: numberOrStringNotRequired,
		userAgent: stringNotRequired,
		uniIdToken: stringNotRequired,
		deviceBrand: stringNotRequired,
		deviceModel: stringNotRequired,
		osVersion: stringNotRequired,
		osLanguage: stringNotRequired,
		osTheme: stringNotRequired,
		romName: stringNotRequired,
		romVersion: stringNotRequired,
		devicePixelRatio: numberNotRequired,
		windowWidth: numberNotRequired,
		windowHeight: numberNotRequired,
		screenWidth: numberNotRequired,
		screenHeight: numberNotRequired
	}
	const validateRes = new Validator().validate(clientInfo, schema)
	if (validateRes) {
		if (validateRes.errCode === ERROR.PARAM_REQUIRED) {
			console.warn(
				'- 如果使用HBuilderX运行本地云函数/云对象功能时出现此提示，请改为使用客户端调用本地云函数方式调试，或更新HBuilderX到3.4.12及以上版本。\n- 如果是缺少clientInfo.appId，请检查项目manifest.json内是否配置了DCloud AppId'
			)
			throw new Error(`"clientInfo.${validateRes.schemaKey}" is required.`)
		} else {
			throw new Error(`Invalid client info: clienInfo.${validateRes.schemaKey}`)
		}
	}
}

module.exports = {
	Validator,
	checkClientInfo
}